﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

namespace System.Peppers.Win32
{
    /// <summary>
    /// Use this class to impersonate other users on the network, you can change LOGON32_LOGON for different effects
    /// -I use it to access UNC paths with credentials like \\computername\sharename
    /// </summary>
    public sealed class Impersonator : IDisposable
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
            LOGON32_LOGON dwLogonType, LOGON32_PROVIDER dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        private enum LOGON32_LOGON : int
        {
            /// <summary>
            /// This logon type is intended for users who will be interactively using the computer, such as a user being logged on  
            /// by a terminal server, remote shell, or similar process.
            /// This logon type has the additional expense of caching logon information for disconnected operations; 
            /// therefore, it is inappropriate for some client/server applications,
            /// such as a mail server.
            /// </summary>
            INTERACTIVE = 2,

            /// <summary>
            /// This logon type is intended for high performance servers to authenticate plaintext passwords.
            /// The LogonUser function does not cache credentials for this logon type.
            /// </summary>
            NETWORK = 3,

            /// <summary>
            /// This logon type is intended for batch servers, where processes may be executing on behalf of a user without 
            /// their direct intervention. This type is also for higher performance servers that process many plaintext
            /// authentication attempts at a time, such as mail or Web servers. 
            /// The LogonUser function does not cache credentials for this logon type.
            /// </summary>
            BATCH = 4,

            /// <summary>
            /// Indicates a service-type logon. The account provided must have the service privilege enabled. 
            /// </summary>
            SERVICE = 5,

            /// <summary>
            /// This logon type is for GINA DLLs that log on users who will be interactively using the computer. 
            /// This logon type can generate a unique audit record that shows when the workstation was unlocked. 
            /// </summary>
            UNLOCK = 7,

            /// <summary>
            /// This logon type preserves the name and password in the authentication package, which allows the server to make 
            /// connections to other network servers while impersonating the client. A server can accept plaintext credentials 
            /// from a client, call LogonUser, verify that the user can access the system across the network, and still 
            /// communicate with other servers.
            /// NOTE: Windows NT:  This value is not supported. 
            /// </summary>
            NETWORK_CLEARTEXT = 8,

            /// <summary>
            /// This logon type allows the caller to clone its current token and specify new credentials for outbound connections.
            /// The new logon session has the same local identifier but uses different credentials for other network connections. 
            /// NOTE: This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
            /// NOTE: Windows NT:  This value is not supported. 
            /// </summary>
            NEW_CREDENTIALS = 9,
        }

        private enum LOGON32_PROVIDER : uint
        {
            /// <summary>
            /// Use the standard logon provider for the system. 
            /// The default security provider is negotiate, unless you pass NULL for the domain name and the user name 
            /// is not in UPN format. In this case, the default provider is NTLM. 
            /// NOTE: Windows 2000/NT:   The default security provider is NTLM.
            /// </summary>
            DEFAULT = 0,
            WINNT50 = 3,
            WINNT40 = 2,
            WINNT35 = 1,
        }

        private IntPtr _token = IntPtr.Zero;
        private WindowsIdentity _identity = null;
        private WindowsImpersonationContext _context = null;

        public Impersonator(string username, string domain, string password)
        {
            if (LogonUser(username, domain, password, LOGON32_LOGON.NEW_CREDENTIALS, LOGON32_PROVIDER.WINNT50, ref _token))
            {
                _identity = new WindowsIdentity(_token);
                _context = _identity.Impersonate();
            }
            else
            {
                throw new Win32Exception();
            }
        }

        public Impersonator(string username, string password)
            : this(username, null, password)
        { }

        ~Impersonator()
        {
            Dispose();
        }

        #region IDisposable Members

        public void Dispose()
        {
            GC.SuppressFinalize(this);

            if (_context != null)
            {
                _context.Dispose();
                _context = null;
            }
            if (_identity != null)
            {
                _identity.Dispose();
                _identity = null;
            }
            if (_token != IntPtr.Zero)
            {
                CloseHandle(_token);
                _token = IntPtr.Zero;
            }
        }

        #endregion
    }
}
